package com.siteseat.common.security.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.siteseat.common.exception.InternalException;
import com.siteseat.common.foundation.Component;
import com.siteseat.common.security.Credential;
import com.siteseat.common.security.CryptoHelper;
import com.siteseat.common.security.Permission;
import com.siteseat.common.security.SecurityException;
import com.siteseat.common.security.SecurityManager;

/**
 *
 */
public class DefaultSecurityManager extends Component implements SecurityManager {

  private Map<String, Credential> credentials = new HashMap<String, Credential>();

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doDestroy() throws Exception {
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doInit() throws Exception {    
  }

  public DefaultSecurityManager( Credential... administrators ) {
    for( Credential a: administrators ) {
      if( a.isAdministrator() == false ) {
        throw new InternalException( a + " is not an administrator" );
      }
      try {
        _addCredential( a );
      } catch( SecurityException e ) {
        throw new InternalException( "Unexpected error", e );
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void grant( String actor, String credentialName, String resource, Permission permission ) throws SecurityException {
    ensureGranted( actor, null, Permission.PERMISSION_GRANT );
    Credential u = ensureCredentialExists( credentialName );
    u.grantPermission( resource, permission );
  }

  /**
   * @param identity
   * @param resource
   * @param permission
   * @throws SecurityException
   */
  protected void _grant( String credentialName, String resource, Permission permission ) throws SecurityException {
    Credential u = ensureCredentialExists( credentialName );
    u.grantPermission( resource, permission );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void grant( String actor, String credentialName, String resource, Set<Permission> permissions )
          throws SecurityException {
    ensureGranted( actor, null, Permission.PERMISSION_GRANT );
    Credential u = ensureCredentialExists( credentialName );
    for( Permission p: permissions ) {
      u.grantPermission( resource, p );
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void revoke( String actor, String credentialName, String resource, Permission permission ) throws SecurityException {
    ensureGranted( actor, null, Permission.PERMISSION_REVOKE );

    Credential u = ensureCredentialExists( credentialName );
    if( u.isAdministrator() ) {
      throw new SecurityException( "Cannot revoke any permission from administrator: " + credentialName );
    }

    u.revokePermission( resource, permission );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void revoke( String actor, String credentialName, String resource, Set<Permission> permissions )
          throws SecurityException {
    ensureGranted( actor, null, Permission.PERMISSION_REVOKE );

    Credential u = ensureCredentialExists( credentialName );
    if( u.isAdministrator() ) {
      throw new SecurityException( "Cannot revoke any permission from administrator: " + credentialName );
    }

    for( Permission p: permissions ) {
      u.revokePermission( resource, p );
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isAdministrator( String credentialName ) throws SecurityException {
    Credential u = ensureCredentialExists( credentialName );
    return u.isAdministrator();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isGranted( String credentialName, String resource, Permission permission ) throws SecurityException {
    Credential u = ensureCredentialExists( credentialName );
    if( u.isAdministrator() ) {
      return true;
    }
    return u.hasPermission( resource, permission );
  }

  /**
   * {@inheritDoc}
   * 
   * @throws InterruptedException
   */
  @Override
  public Credential authenticate( String credentialName, String password, String clientId ) {
    Credential r = this.credentials.get( credentialName );
    if( r == null ) {
      log.warn( "Invalid credential name: " + credentialName + ", password=" + password + ", clientId=" + clientId );
      return null;
    }
    
    Long prevSalt = (Long)r.getSalt( clientId );
    if( prevSalt == null ) {
      prevSalt = Long.valueOf( 0 );
    }
    
    Long newSalt;
    try {
      newSalt = CryptoHelper.crudeVerify( credentialName, r.getPassword(), password, clientId, prevSalt.longValue() );
    } catch( SecurityException e ) {
      log.warn( "Error during authentication", e );
      return null;
    }
    if( newSalt == null ) {
      log.warn( "Invalid salt: " + credentialName + ", password=" + password + ", clientId=" + clientId );
      return null;
    }
    
    r.setSalt( clientId, newSalt );
    
    return r;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void addCredential( String actor, Credential credential ) throws SecurityException {
    if( credential.isAdministrator() ) {
      ensureGranted( actor, null, Permission.ADMINISTRATOR_ADD );
    } else {
      ensureGranted( actor, null, Permission.CREDENTIAL_ADD );
    }
    _addCredential( credential );
  }

  /**
   * @param credentialName
   * @param password
   * @throws SecurityException
   */
  protected void _addCredential( Credential credential ) throws SecurityException {
    credential.validate();

    String credentialName = credential.getName();
    if( this.credentials.containsKey( credentialName ) ) {
      throw new SecurityException( "Duplicated credential name: <" + credentialName + ">" );
    }
    this.credentials.put( credentialName, credential );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean hasCredential( String credentialName ) {
    return this.credentials.containsKey( credentialName );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void removeCredential( String actor, String credentialName, String password ) throws SecurityException {
    Credential u = ensureCredentialExists( credentialName );

    if( u.isAdministrator() ) {
      ensureGranted( actor, null, Permission.ADMINISTRATOR_REMOVE );
    } else {
      ensureGranted( actor, null, Permission.CREDENTIAL_REMOVE );
      if( u.getPassword().equals( password ) ) {
        throw new SecurityException( "Password not matched" );
      }
    }

    this.credentials.remove( credentialName );
  }

  /**
   * @param credentialName
   * @return
   * @throws SecurityException
   */
  protected Credential ensureCredentialExists( String credentialName ) throws SecurityException {
    Credential r = this.credentials.get( credentialName );
    if( r == null ) {
      throw new SecurityException( "credential name: <" + credentialName + "> is not found" );
    }
    return r;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void changePassword( String actor, String credentialName, String oldPassword, String newPassword )
          throws SecurityException {
    Credential u = ensureCredentialExists( credentialName );

    if( u.isAdministrator() ) {
      ensureGranted( actor, null, Permission.ADMINISTRATOR_CHANGE_PASSWORD );
    } else {
      ensureGranted( actor, null, Permission.CREDENTIAL_CHANGE_PASSWORD );
      if( u.getPassword().equals( oldPassword ) == false ) {
        throw new SecurityException( "Old password not matched" );
      }
    }
    
    u.setPassword( newPassword );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void ensureGranted( String actor, String resource, Permission permission ) throws SecurityException {
    if( isGranted( actor, resource, permission ) == false ) {
      throw new SecurityException( actor + " is not authorized permission <" + permission + "> resource <" + resource
              + ">" );
    }
  }

}
